#include "Frustum.h"
#include "BoundingBox.h"
#include "BoundingSphere.h"
#include "Rect.h"
#include "Ray.h"

#include <glm/gtc/matrix_transform.hpp>

Frustum::Frustum()
{
	Define(glm::mat4(1.0f));
}

Frustum::Frustum(const glm::mat4& transform)
{
	Define(transform);
}

Frustum::Frustum(const glm::mat4& projection, const glm::mat4& view)
{
	glm::mat4 m = projection * view;
	Define(m);
}

Frustum::~Frustum()
{
}

void Frustum::Define(const glm::mat4& projection, const glm::mat4& view)
{

	glm::mat4 m = projection * view;
	Define(m);
}

void Frustum::Transform(const glm::mat4& transform)
{

	for (int i = 0; i < 6; i++)
	{
		mPlanes[i].Transform(transform);
	}

	for (int i = 0; i < 6; i++)
	{
		mPlanes[i].Normalise();
	}

	CalculateVertices(transform);
}

void Frustum::Define(const glm::mat4& transform)
{
	auto& m = transform;
	mPlanes[FrustumPlane::Left] = Plane(m[0][3] + m[0][0], m[1][3] + m[1][0], m[2][3] + m[2][0], m[3][3] + m[3][0]);
	mPlanes[FrustumPlane::Right] = Plane(m[0][3] - m[0][0], m[1][3] - m[1][0], m[2][3] - m[2][0], m[3][3] - m[3][0]);
	mPlanes[FrustumPlane::Down] = Plane(m[0][3] + m[0][1], m[1][3] + m[1][1], m[2][3] + m[2][1], m[3][3] + m[3][1]);
	mPlanes[FrustumPlane::Up] = Plane(m[0][3] - m[0][1], m[1][3] - m[1][1], m[2][3] - m[2][1], m[3][3] - m[3][1]);
	mPlanes[FrustumPlane::Near] = Plane(m[0][3] + m[0][2], m[1][3] + m[1][2], m[2][3] + m[2][2], m[3][3] + m[3][2]);
	mPlanes[FrustumPlane::Far] = Plane(m[0][3] - m[0][2], m[1][3] - m[1][2], m[2][3] - m[2][2], m[3][3] - m[3][2]);

	for (int i = 0; i < 6; i++)
	{
		mPlanes[i].Normalise();
	}

	CalculateVertices(transform);
}

void Frustum::DefineOrtho(float scale, float aspectRatio, float near, float far, const glm::mat4& viewMatrix)
{

	glm::mat4 m = glm::ortho(-scale * aspectRatio, scale * aspectRatio, -scale, scale, near, far);
	m = m * viewMatrix;
	Define(m);
}
void Frustum::Define(float fov, float aspectRatio, float near, float far, const glm::mat4& viewMatrix)
{
	float tangent = tan(fov * 0.5f);
	float height = near * tangent;
	float width = height * aspectRatio;

	glm::mat4 m = glm::frustum(-width, width, -height, height, near, far);
	m = m * viewMatrix;
	Define(m);
}

bool Frustum::IsInside(const glm::vec3& point) const
{
	for (int i = 0; i < 6; i++)
	{
		if (mPlanes[i].Distance(point) < 0.0f)
		{
			return false;
		}
	}

	return true;
}

bool Frustum::IsInside(const BoundingSphere& sphere) const
{
	for (int i = 0; i < 6; i++)
	{
		if (mPlanes[i].Distance(sphere.GetCenter()) < -sphere.GetRadius())
		{
			return false;
		}
	}

	return true;
}

bool Frustum::IsInside(const BoundingBox& box) const
{
	for (int i = 0; i < 6; i++)
	{
		glm::vec3 p = box.Min(), n = box.Max();
		glm::vec3 N = mPlanes[i].Normal();
		if (N.x >= 0)
		{
			p.x = box.Max().x;
			n.x = box.Min().x;
		}
		if (N.y >= 0)
		{
			p.y = box.Max().y;
			n.y = box.Min().y;
		}
		if (N.z >= 0)
		{
			p.z = box.Max().z;
			n.z = box.Min().z;
		}

		if (mPlanes[i].Distance(p) < 0)
		{
			return false;
		}
	}
	return true;
}

bool Frustum::IsInside(const Rect& rect) const
{
	for (int i = 0; i < 6; i++)
	{
		glm::vec3 N = mPlanes[i].Normal();
		if (N.x >= 0)
		{
			if (mPlanes[i].Distance(glm::vec3(rect.GetPosition(), 0)) < 0)
			{
				return false;
			}
		}
		else
		{
			if (mPlanes[i].Distance(glm::vec3(rect.GetPosition().x + rect.GetSize().x, rect.GetPosition().y, 0)) < 0)
			{
				return false;
			}
		}
	}

	return true;
}

bool Frustum::IsInside(const Ray& ray) const
{
	for (int i = 0; i < 6; i++)
	{
		if (mPlanes[i].Distance(ray.mOrigin) < 0.0f)
		{
			return false;
		}
	}

	return true;
}

bool Frustum::IsInside(const Plane& plane) const
{
	for (int i = 0; i < 6; i++)
	{
		if (mPlanes[i].Distance(plane.Normal()) < 0.0f)
		{
			return false;
		}
	}

	return true;
}

const Plane& Frustum::GetPlane(FrustumPlane plane) const
{
	return mPlanes[plane];
}

glm::vec3* Frustum::GetVerticies()
{

	return mVerticies;
}

void Frustum::CalculateVertices(const glm::mat4& transform)
{
	bool zerotoOne = false;
	bool leftHand = true;
	glm::mat4 transformInv = glm::inverse(transform);
	mVerticies[0] = glm::vec4(-1.0f, -1.0f, zerotoOne ? 0.0f : -1.0f, 1.0f);
	mVerticies[1] = glm::vec4(1.0f, -1.0f, zerotoOne ? 0.0f : -1.0f, 1.0f);
	mVerticies[2] = glm::vec4(1.0f, 1.0f, zerotoOne ? 0.0f : -1.0f, 1.0f);
	mVerticies[3] = glm::vec4(-1.0f, 1.0f, zerotoOne ? 0.0f : -1.0f, 1.0f);

	mVerticies[4] = glm::vec4(-1.0f, -1.0f, 1.0f, 1.0f);
	mVerticies[5] = glm::vec4(1.0f, -1.0f, 1.0f, 1.0f);
	mVerticies[6] = glm::vec4(1.0f, 1.0f, 1.0f, 1.0f);
	mVerticies[7] = glm::vec4(-1.0f, 1.0f, 1.0f, 1.0f);

	glm::vec4 temp;
	for (int i = 0; i < 8; i++)
	{
		temp = transformInv * glm::vec4(mVerticies[i], 1.0f);
		mVerticies[i] = temp / temp.w;
	}
}
